#include <ds/lru.h>
#include <mm/valloc.h>

lru_t* lru_init(int (*destory)(lnode_t*)) {
    lru_t* lru = valloc(sizeof(lru_t));
    if (!lru)
        return NULL;

    lru->destory = destory;

    list_head_init(&lru->head);
    mutex_init(&lru->lock);
    lru->size = 0;
    return lru;
}

void lru_use_one(lru_t* lru, lnode_t* elem) {
    mutex_lock(&lru->lock);
    if (elem->next && elem->prev && !list_empty(elem)) {
        lru->size--;
        list_remove(elem);
    }
    list_prepend(&lru->head, elem);
    lru->size++;
    mutex_unlock(&lru->lock);
}

static void do_destory(lru_t* lru, lnode_t* elem) {
    list_remove(elem);
    if (!lru->destory(elem)) {
        list_prepend(&lru->head, elem);
    } else {
        lru->size--;
    }
}

void lru_evict_one(lru_t* lru) {
    lnode_t* elem = lru->head.prev;
    if (elem == &lru->head) {
        return;
    }
    mutex_lock(&lru->lock);
    do_destory(lru, elem);
    mutex_unlock(&lru->lock);
}

void lru_remove(lru_t* lru, lnode_t* elem) {
    mutex_lock(&lru->lock);
    if (elem->next && elem->prev && !list_empty(elem)) {
        lru->size--;
        list_remove(elem);
    }
    mutex_unlock(&lru->lock);
}

void lru_evict_count(lru_t* lru, size_t count) {
    mutex_lock(&lru->lock);
    lnode_t* elem = lru->head.prev, * prev;
    while (elem != &lru->head && count) {
        prev = elem->prev;
        do_destory(lru, elem);
        elem = prev;
        count--;
    }
    mutex_unlock(&lru->lock);
}

void lru_evict_half(lru_t* lru) {
    int size = lru->size / 2;
    lru_evict_count(lru, size);
}

bool lru_must_evict(lru_t* lru, size_t count) {
    if (lru->size < count) {
        return false;
    }
    mutex_lock(&lru->lock);
    lnode_t* elem = lru->head.prev, * prev;
    while (count) {
        prev = elem->prev;

        if (elem == &lru->head) {
            prev = elem->prev;
        }

        
        list_remove(elem);
        if (!lru->destory(elem)) {
            list_prepend(&lru->head, elem);
            count++;
        } else {
            lru->size--;
        }
        
        elem = prev;
        count--;
    }
    mutex_unlock(&lru->lock);
    return true;
}